package com.myweixin.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.log4j.Logger;

/**
 * 
 * @author liuzy
 * @since 2016年8月30日
 */
@Intercepts({ //
		@Signature(method = "update", type = Executor.class, args = { MappedStatement.class, Object.class }), //
		@Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }), //
})
public class ShowSql implements Interceptor {
	private Logger logger = Logger.getLogger(ShowSql.class);

	private boolean showSql;
	private boolean showFullSql;
	private boolean formatSql;
	private String sql;
	private List<Object> pms;

	@Override
	public void setProperties(Properties properties) {
		this.setShowSql("true".equals(properties.getProperty("showSql")));
		this.setShowFullSql("true".equals(properties.getProperty("showFullSql")));
		this.setFormatSql("true".equals(properties.getProperty("formatSql")));
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		try {
			Object[] args = invocation.getArgs();
			MappedStatement mappedStatement = (MappedStatement) args[0];
			Object parameterObject = args[1];
			BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
			setSql(boundSql.getSql());
			setPms(mappedStatement, parameterObject, boundSql);
			if (showSql) {
				logger.info("sql: " +  sql);
				if (pms != null && !pms.isEmpty()) {
					StringBuilder sb = new StringBuilder();
					for (Object value : pms) {
						if (value == null)
							sb.append("null, ");
						else
							sb.append(value + "(" + value.getClass().getSimpleName() + "), ");
					}
					logger.info("pms: " + sb.substring(0, sb.length() - 2));
				}
			}
			if (showFullSql) {
				String sql = this.sql;
				if (pms != null && !pms.isEmpty()) {
					for (Object value : pms) {
						if (value == null) {
							sql = sql.replaceFirst("\\?", "null");
						} else if (value instanceof Integer || value instanceof Long || value instanceof Float || value instanceof Double) {
							sql = sql.replaceFirst("\\?", value.toString());
						} else {
							sql = sql.replaceFirst("\\?", "'" + value.toString() + "'");
						}
					}
				}
				logger.info("==>: " + (formatSql ? format(sql) : sql));
			}
		} catch (Exception e) {
			logger.error("ShowSql failed", e);
		}
		return invocation.proceed();
	}

	public void setSql(String sql) {
		sql = sql.replaceAll("\r", " ");
		sql = sql.replaceAll("\n", " ");
		sql = sql.replaceAll("\t", " ");
		while (sql.contains("  ")) {
			sql = sql.replaceAll("  ", " ");
		}
		this.sql = sql.trim();
	}

	private void setPms(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		if (parameterMappings != null && !parameterMappings.isEmpty()) {
			pms = new ArrayList<Object>();
			for (int i = 0; i < parameterMappings.size(); i++) {
				ParameterMapping parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					Object value;
					String propertyName = parameterMapping.getProperty();
					if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if (parameterObject == null) {
						value = null;
					} else if (mappedStatement.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else {
						MetaObject metaObject = mappedStatement.getConfiguration().newMetaObject(parameterObject);
						value = metaObject.getValue(propertyName);
					}
					pms.add(value);
				}
			}
		}
	}

	public String format(String sql) {
		return FormatSql.format(sql);
	}

	public boolean isShowSql() {
		return showSql;
	}

	public void setShowSql(boolean showSql) {
		this.showSql = showSql;
	}

	public boolean isShowFullSql() {
		return showFullSql;
	}

	public void setShowFullSql(boolean showFullSql) {
		this.showFullSql = showFullSql;
	}

	public boolean isFormatSql() {
		return formatSql;
	}

	public void setFormatSql(boolean formatSql) {
		this.formatSql = formatSql;
	}

}
